#
# This file is a part of the open-eBackup project.
# This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
# If a copy of the MPL was not distributed with this file, You can obtain one at
# http://mozilla.org/MPL/2.0/.
#
# Copyright (c) [2024] Huawei Technologies Co.,Ltd.
#
# THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
# EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
# MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
#

import sys
import time

from common.common_models import LogDetail, SubJobDetails

from common.common import exter_attack, get_host_sn
from common.const import SysData, DBLogLevel
from common.parse_parafile import ParamFileUtil
from exchange import log
from exchange.commons.common import get_key_value, get_mailbox_database_active_copy, traverse_folder, \
    report_job_details_by_rpc
from exchange.commons.const import ExchangeReportDBLabel, ExchangeJobInfo, ExchangeProgressPhase, \
    AgentConstant, SubJobStatusEnum
from exchange.commons.exchange_exception import ExchangeErrCodeException, ExchangeInternalException
from exchange.commons.exchange_function_tool import log_start_end, progress_notify_with_job_info, \
    out_result_with_job_info
from exchange.commons.exchange_param_parse import ExchangeParamParse
from exchange.restore.exchange_restore_base import ExchangeRestoreBase
from exchange.restore.restore_base.base_exchange_restore_sub_job import ExchangeRestoreSubJob, \
    report_restore_table_failed_progress
from exchange.restore.restore_param_parser import RestoreParamParser



class DagRestoreSubJob(ExchangeRestoreSubJob, ExchangeRestoreBase):
    def __init__(self, thread_pid, thread_job_id, thread_sub_job_id, param):
        ExchangeRestoreSubJob.__init__(self, thread_pid, param)
        ExchangeRestoreBase.__init__(self, thread_pid, thread_job_id, thread_sub_job_id, param)
        self._pid = thread_pid
        self._progress = None

    @log_start_end()
    @out_result_with_job_info()
    @progress_notify_with_job_info(ExchangeReportDBLabel.SUB_JOB_SUCCESS, ExchangeReportDBLabel.SUB_JOB_FALIED)
    def do_sub_job_task(self, job_info: ExchangeJobInfo):
        try:
            return self.restore(job_info)
        except Exception as error:
            log.error(error, exc_info=True)
            return False

    def restore(self, job_info: ExchangeJobInfo):
        self.report_restore_sub_start(job_info)
        self.create_report_restore_progress_thread(job_info)
        database_list = self.get_local_restore_databases(job_info)
        # 初始化进度
        start_progress = 0
        if database_list is not None and len(database_list) > 0:
            self._progress = int(ExchangeProgressPhase.TASK_COMPLETE /
                                 (len(database_list) * ExchangeProgressPhase.RESTORE_PHASE))
            start_progress = self._progress
        progress_flag = 0
        for database in database_list:
            progress_flag = progress_flag + 1
            # 恢复前准备
            self.set_progress(start_progress, ExchangeProgressPhase.RESTORE_STEP_ONE, progress_flag)
            ret = self.do_restore_preparation(job_info, database)
            if not ret:
                return False
            # 调用Vss恢复
            self.set_progress(start_progress, ExchangeProgressPhase.RESTORE_STEP_TOW, progress_flag)
            ret = self.do_restore_with_vss(job_info, database)
            if not ret:
                return False
            # 环境清理
            self.set_progress(start_progress, ExchangeProgressPhase.RESTORE_STEP_THREE, progress_flag)
            ret = self.do_restore_clean_up(job_info, database)
            if not ret:
                return False
        # 上报任务结束
        self.report_dag_restore_complete(job_info, database_list)
        return True

    def report_dag_restore_complete(self, job_info, database_list):
        self._progress = 100
        data_total_size = self.get_restore_size(database_list)
        log_detail = LogDetail(logInfo=ExchangeReportDBLabel.SUB_JOB_SUCCESS,
                               logInfoParam=[job_info.sub_job_id],
                               logLevel=DBLogLevel.INFO.value)
        output = SubJobDetails(taskId=job_info.job_id, subTaskId=job_info.sub_job_id, progress=self._progress,
                               logDetail=[log_detail], taskStatus=SubJobStatusEnum.COMPLETED.value,
                               dataSize=int(data_total_size / 1024)).dict(by_alias=True)
        report_job_details_by_rpc(job_info.job_id, job_info.pid, job_info.sub_job_id, output)

    def set_progress(self, start_progress, step, progress_flag):
        self._progress = start_progress * step * progress_flag

    @log_start_end()
    def do_restore_clean_up(self, job_info, database):
        target_name = database.get("source_target_name", "")
        # 获取节点ip
        node_ip = ExchangeParamParse.get_restore_node_ip(self._param)
        # 自动装载数据库
        auto_mount = RestoreParamParser.get_auto_mount(self._param)
        if "true" == auto_mount:
            try:
                self.mount_exchange_database(target_name)
                self.report_restore_table_success_progress(
                    job_info, node_ip, ExchangeReportDBLabel.DATABASE_RESTORE_MOUNT_SUCCESS, target_name, 0)
            except ExchangeInternalException:
                report_restore_table_failed_progress(
                    job_info, node_ip, ExchangeReportDBLabel.DATABASE_RESTORE_MOUNT_FAILED, target_name)
                return False
            # 恢复DAG组的被动副本复制功能
            try:
                self._update_passive_copy_seed(target_name)
                self.report_restore_table_success_progress(
                    job_info, node_ip, ExchangeReportDBLabel.DATABASE_SET_RESTORE_SUCCESS, target_name, 0)
            except ExchangeInternalException:
                report_restore_table_failed_progress(
                    job_info, node_ip, ExchangeReportDBLabel.DATABASE_SET_RESTORE_FAILED, target_name)
                return False
        return True

    @log_start_end()
    def do_restore_with_vss(self, job_info, database):
        # 查询目标数据库log路径和edb路径
        # 原位置 查询数据库名称对应的log路径和edb路径
        # 新位置 自定义路径使用自定义路径恢复，未自定义路径查询数据库名称对应的log路径和edb路径
        target_name = database.get("source_target_name", "")
        source_name = database.get("source_db_name", "")
        if not self.restore_single_database(job_info, source_name, target_name):
            return False
        return True

    @log_start_end()
    def get_local_restore_databases(self, job_info: ExchangeJobInfo):
        database_list = []
        # 获取当前节点的主数据库
        database_name_maps = RestoreParamParser.get_database_name_maps(self._param)
        # 获取当前节点服务名称
        mailbox_server = self._json_param.get("subJob", {}).get("jobName")
        log.info(f"local mailbox_server is : {mailbox_server}")
        node_id = get_host_sn()
        nodes = self._param.get("job").get("targetEnv").get("nodes")
        for node in nodes:
            if node_id == node['id']:
                mailbox_server = node["name"]
                log.info(f"local mailbox_server is : {mailbox_server}")
                break
        flags, m_username = get_key_value(f'job_targetEnv_auth_authKey_{job_info.pid}')
        flags, m_password = get_key_value(f'job_targetEnv_auth_authPwd_{job_info.pid}')
        for target_database_name in database_name_maps:
            copy_info = get_mailbox_database_active_copy(m_username, m_password,
                                                         target_database_name.get("source_target_name"))
            if copy_info.get("mailboxServer").upper() == mailbox_server.upper():
                database_list.append(target_database_name)
        log.info(f"local restore database name :{database_list}")
        return database_list

    @log_start_end()
    def do_restore_preparation(self, job_info, database):
        """
        恢复任务准备工作
        1. 检查目标环境exchange server版本是否一致
        2. 检查目标环境是否存在同名
        3. 停止DAG组被动副本复制
        4. 卸载数据库
        5. 检查数据库卸载情况
        6. 设置数据库可还原覆盖属性
        """
        target_name = database.get("source_target_name", "")
        # 停止复制
        node_ip = ExchangeParamParse.get_restore_node_ip(self._param)
        try:
            self._suspend_passive_copy(target_name)
            self.report_restore_table_success_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_SET_RESTORE_SUCCESS, target_name, 0)
        except ExchangeInternalException:
            report_restore_table_failed_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_SET_RESTORE_FAILED, target_name)
            return False

        log.info("stop copy success")
        # 设置数据库可被还原覆盖属性
        try:
            self._set_exchange_database_allow_restore(target_name)
            self.report_restore_table_success_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_ALLOW_RESTORE_SUCCESS, target_name, 0)
        except ExchangeInternalException:
            log.info(f"Do restore preparation failed, set allow {target_name} restore fail")
            report_restore_table_failed_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_ALLOW_RESTORE_FAILED, target_name)
            return False

        log.info("set allow restore success")
        # 卸载数据库
        auto_dismount = RestoreParamParser.get_auto_dismount(self._param)
        if "true" == auto_dismount:
            try:
                self._dismount_exchange_database(target_name)
            except ExchangeInternalException:
                log.info(f"Do restore preparation failed, dismount {target_name} fail")
                report_restore_table_failed_progress(
                    job_info, node_ip, ExchangeReportDBLabel.DATABASE_RESTORE_DISMOUNT_FAILED, target_name)
                return False

        log.info("dismount success")
        # 检查数据库活动副本卸载情况
        exchange_dismount_status = False
        for _ in range(AgentConstant.RETRY_TIMES):
            exchange_dismount_status = self._check_exchange_database_dismounted_status(target_name)
            if exchange_dismount_status:
                break
            else:
                log.warn(f"Failed getting Microsoft Exchange dismount status,\
                                        wait for {AgentConstant.RETRY_WAIT_SECONDS} seconds")
                time.sleep(AgentConstant.RETRY_WAIT_SECONDS)
        # 上报进度
        if exchange_dismount_status:
            self.report_restore_table_success_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_RESTORE_DISMOUNT_SUCCESS, target_name, 0)
        else:
            report_restore_table_failed_progress(
                job_info, node_ip, ExchangeReportDBLabel.DATABASE_RESTORE_UNMOUNT, target_name)
        log.info("check dismount complete")
        return exchange_dismount_status


@exter_attack
def do_exec(thread_pid, task_job_id, task_sub_job_id):
    try:
        param = ParamFileUtil.parse_param_file(thread_pid)
        job = DagRestoreSubJob(thread_pid, task_job_id, task_sub_job_id, param)
        job.do_sub_job_task(ExchangeParamParse.get_restore_job_info(thread_pid, task_job_id, task_sub_job_id, param))
    except ExchangeErrCodeException as err_str:
        log.error(f"Restore sub job failed, err: {err_str}, pid:{pid}")
        return False
    return True


if __name__ == '__main__':
    for line in sys.stdin:
        SysData.SYS_STDIN = line
        break

    if len(sys.argv) < 1:
        log.error("Param is wrong.")
        sys.exit(1)

    args_list = sys.argv[1:]
    log.info(f"get args_list: {args_list}")
    pid = args_list[0]
    job_id = ""
    sub_job_id = ""
    if len(args_list) >= 2:
        job_id = args_list[1]
    if len(args_list) == 3:
        sub_job_id = args_list[2]
    try:
        do_exec(pid, job_id, sub_job_id)
    except Exception as err:
        log.error(f"Err: {err}, pid: {pid}")
        sys.exit(0)

    sys.exit(0)
